/*
 * Copyright (C) 2008-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#define top_ast()			ast_vector[parser_vector->offset - 1]

/*
 * Prototypes
 */
static etoken_t
statement_noeof(void);

static etoken_t
statement(void);

static void
label_define(east_t *ast);

static void
case_value(void);

static void
case_check_value(east_t *block, east_t *ast);

static void
case_check_range(east_t *block, east_t *ast);

static void
switch_case(east_t *ast);

static void
statement_paren_comma_list(void);

static east_t *
get_block(etoken_t token, east_t *ast);

static esymbol_t *
declare(east_t *type, east_t *decl);

static etoken_t
declaration(void);

static efunction_t *
prototype(etag_t *tag, east_t *decl);

static void
function(void);

static etoken_t
structure(void);

static etoken_t
definition(void);

static void
group_match(ebool_t data, etoken_t token);

static etoken_t
group(ebool_t data);

static etoken_t
precedence_noeof(void);

static etoken_t
precedence(void);

static etoken_t
expression_noeof(void);

static etoken_t
expression(void);

static etoken_t
binary_assign(void);

static etoken_t
binary_left(etoken_t token);

static void
binary_right(etoken_t token);

static etoken_t
unary_noeof(void);

static etoken_t
unary(void);

static etoken_t
unary_loop(etoken_t token);

static etoken_t
unary_value(etoken_t token);

static etoken_t
unary_decl(void);

static etoken_t
unary_list(void);

static etoken_t
unary_vector(void);

static etoken_t
unary_field(void);

static etoken_t
unary_unary(etoken_t token);

static etoken_t
unary_binary(etoken_t token);

static etoken_t
primary_noeof(void);

static etoken_t
primary(void);

static etoken_t
lookahead(void);

static etoken_t
primary(void);

static etoken_t
tokenize(void);

static void
consume(void);

static void
discard(void);

static void
push_ast(east_t *ast);

static east_t *
pop_ast(void);

static void
push_block(east_t *ast);

static void
pop_block(void);

/*
 * Initialization
 */
eobject_t		 object_null;

static struct {
    char	*name;
    etoken_t	 token;
} keywords[] = {
    /* macros */
    { "__FILE__",	tok_FILE	},
    { "__LINE__",	tok_LINE	},
    { "__FUNCTION__",	tok_FUNCTION	},
    { "__VA_ARGS__",	tok_VA_ARGS	},
    { "__DATE__",	tok_DATE	},
    { "__TIME__",	tok_TIME	},
    { "__COUNTER__",	tok_COUNTER	},
    { "#",		tok_stringify	},
    { "##",		tok_concat	},
    { "atan2", 		tok_atan2	},
    { "pow", 		tok_pow		},
    { "hypot",		tok_hypot	},
    { "complex",	tok_complex	},
    { "subtypeof",	tok_subtypeof	},
    { "renew",		tok_renew	},
    { "sizeof",		tok_sizeof	},
    { "typeof",		tok_typeof	},
    { "new",		tok_new		},
    { "rankof",		tok_rankof	},
    { "signbit",	tok_signbit	},
    { "signum",	 	tok_signum	},
    { "rational",	tok_rational	},
    { "integer_p",	tok_integer_p	},
    { "rational_p",	tok_rational_p	},
    { "float_p",	tok_float_p	},
    { "real_p",	 	tok_real_p	},
    { "complex_p",	tok_complex_p	},
    { "number_p",	tok_number_p	},
    { "finite_p",	tok_finite_p	},
    { "inf_p",	 	tok_inf_p	},
    { "nan_p",	 	tok_nan_p	},
    { "num", 		tok_num		},
    { "den", 		tok_den		},
    { "real", 		tok_real	},
    { "imag", 		tok_imag	},
    { "arg", 		tok_arg		},
    { "conj", 		tok_conj	},
    { "floor", 		tok_floor	},
    { "trunc", 		tok_trunc	},
    { "round", 		tok_round	},
    { "ceil", 		tok_ceil	},
    { "abs", 		tok_abs		},
    { "sqrt", 		tok_sqrt	},
    { "cbrt",		tok_cbrt	},
    { "sin",		tok_sin		},
    { "cos",		tok_cos		},
    { "tan",		tok_tan		},
    { "asin",		tok_asin	},
    { "acos",		tok_acos	},
    { "atan",		tok_atan	},
    { "sinh",		tok_sinh	},
    { "cosh",		tok_cosh	},
    { "tanh",		tok_tanh	},
    { "asinh",		tok_asinh	},
    { "acosh",		tok_acosh	},
    { "atanh",		tok_atanh	},
    { "proj",		tok_proj	},
    { "exp",		tok_exp		},
    { "log",		tok_log		},
    { "log2",		tok_log2	},
    { "log10",		tok_log10	},
    { "this",		tok_this	},
    { "if",		tok_if		},
    { "else",		tok_else	},
    { "goto",		tok_goto	},
    { "return",		tok_return	},
    { "switch",		tok_switch	},
    { "case",		tok_case	},
    { "default",	tok_default	},
    { "break",		tok_break	},
    { "for", 		tok_for		},
    { "do",		tok_do		},
    { "while",		tok_while	},
    { "continue",	tok_continue	},
    { "try", 		tok_try		},
    { "catch",		tok_catch	},
    { "throw",		tok_throw	},
    { "always",		tok_always	},
    { "class",		tok_class	},
};
static east_t		 *root_ast;
static east_t		 *head_ast;
static east_t		**ast_vector;
static evector_t	 *block_vector;
static evector_t	 *parser_vector;

/*
 * Implementation
 */
void
init_parser(void)
{
    eint32_t		 offset;
    esymbol_t		*symbol;

    /* Declare language keywords / operators */
    for (offset = 0; offset < esize(keywords); offset++) {
	symbol = enew_identifier(eget_string((euint8_t *)keywords[offset].name,
					     strlen(keywords[offset].name)));
	enew_word(&symbol->value, keywords[offset].token);
	symbol->keyword = 1;
    }

    object_null = enew_identifier(eget_string((euint8_t *)"null", 4));

    eadd_root((eobject_t *)&root_ast);
    eadd_root((eobject_t *)&head_ast);
    eadd_root((eobject_t *)&block_vector);
    enew_vector((eobject_t *)&block_vector, t_void, 16);
    eadd_root((eobject_t *)&parser_vector);
    enew_vector((eobject_t *)&parser_vector, t_void, 16);
    ast_vector = parser_vector->v.obj;
}

void
finish_parser(void)
{
    erem_root((eobject_t *)&root_ast);
    erem_root((eobject_t *)&head_ast);
    erem_root((eobject_t *)&block_vector);
    erem_root((eobject_t *)&parser_vector);
}

eobject_t
eparse(void)
{
    east_t		*ast;
    east_t		*tail;

    for (;;) {
	if (statement() == tok_eof) {
	    assert(parser_vector->offset == 0);
	    return (root_ast);
	}
	ast = pop_ast();
	if (root_ast == null)
	    root_ast = tail = ast;
	else {
	    tail->next = ast;
	    tail = ast;
	}
    }

    return (null);
}

void
enew_ast(eobject_t *pointer, etoken_t token,
	 evector_t *name, eint32_t lineno, eint32_t column)
{
    east_t		*ast;

    ast = eget_ast(pointer);
    ast->token = token;
    ast->note.name = name;
    ast->note.lineno = lineno;
    ast->note.column = column;
}

static etoken_t
statement_noeof(void)
{
    etoken_t		token = statement();

    if (token == tok_eof)
	eread_error("unexpected end of file");

    return (token);
}

static etoken_t
statement(void)
{
    east_t		*ast;
    etoken_t		 token;
    evector_t		*vector;
    eobject_t		*pointer;

    token = lookahead();
    switch (token) {
	case tok_eof:
	    return (token);
	case tok_obrace:
	    /* brace is code, not data */
	    primary();
	    break;
	default:
	    token = expression();
	    break;
    }
    ast = top_ast();
    switch (token) {
	case tok_label:
	    label_define(ast);
	    break;
	case tok_decl:
	    token = declaration();
	    break;
	case tok_symbol:
	    vector = ((esymbol_t *)ast->l.value)->name;
	    if (eget_bound_symbol(vector) == null)
		eparse_error(ast, "undefined symbol '%p'", vector);
	case tok_number:
	    switch (lookahead()) {
		case tok_comma:
		    eparse_warn(ast, "expression has no effect");
		    discard();
		    consume();
		    token = statement();
		    break;
		case tok_semicollon:
		    eparse_warn(ast, "statement has no effect");
		    consume();
		    edel_object(&ast->l.value);
		    ast->token = token = tok_stat;
		    break;
		default:
		    eparse_error(ast, "expecting ',' or ';' %A", ast);
	    }
	    break;
	case tok_class:
	    token = definition();
	    break;
	case tok_list:			case tok_expr:
	    token = tok_stat;
	    gc_ref(pointer);
	    enew_ast(pointer, tok_stat, top_ast()->note.name,
		     top_ast()->note.lineno, top_ast()->note.column);
	    gc_pop(ast);
	    ast->l.ast = top_ast();
	    top_ast() = ast;
	    ast = ast->l.ast;
	    switch (lookahead()) {
		case tok_comma:
		    consume();
		    group_match(false, tok_semicollon);
		    ast->next = pop_ast();
		case tok_semicollon:
		    consume();
		    break;
		default:
		    eparse_error(ast, "expecting ',' or ';' %A", ast);
	    }
	    break;
	case tok_obrace:
	    token = ast->token = tok_code;
	    ast = null;
	    while (lookahead() != tok_cbrace) {
		statement_noeof();
		if (ast == null) {
		    ast = pop_ast();
		    top_ast()->l.value = ast;
		    ast = top_ast()->l.value;
		}
		else {
		    ast->next = pop_ast();
		    ast = ast->next;
		}
	    }
	    if (ast == null)
		top_ast()->l.value = null;
	    consume();
	    break;
	case tok_semicollon:
	    ast->token = token = tok_stat;
	    break;
	case tok_if:
	    statement_paren_comma_list();
	    ast->t.ast = pop_ast();
	    statement_noeof();
	    ast->l.ast = pop_ast();
	    if (lookahead() == tok_else) {
		consume();
		statement_noeof();
		ast->r.ast = pop_ast();
	    }
	    break;
	case tok_while:
	    statement_paren_comma_list();
	    ast->t.ast = pop_ast();
	    push_block(ast);
	    statement_noeof();
	    pop_block();
	    ast->c.ast = pop_ast();
	    break;
	case tok_for:
	    if (primary() != tok_oparen)
		eparse_error(top_ast(), "expecting '(' %A", top_ast());
	    discard();
	    group_match(false, tok_semicollon);
	    consume();
	    ast->l.ast = pop_ast();		/* init */
	    group_match(false, tok_semicollon);
	    consume();
	    ast->t.ast = pop_ast();
	    group_match(false, tok_cparen);
	    consume();
	    ast->r.ast = pop_ast();		/* increment */
	    push_block(ast);
	    statement_noeof();
	    pop_block();
	    ast->c.ast = pop_ast();
	    break;
	case tok_do:
	    push_block(ast);
	    statement_noeof();
	    pop_block();
	    ast->c.ast = pop_ast();
	    if (primary() != tok_while)
		eparse_error(top_ast(), "expecting 'while' %A", top_ast());
	    discard();
	    statement_paren_comma_list();
	    ast->t.ast = pop_ast();
	    if (primary() != tok_semicollon)
		eparse_error(top_ast(), "expecting ';' %A", top_ast());
	    discard();
	    break;
	case tok_switch:
	    statement_paren_comma_list();
	    ast->t.ast = pop_ast();
	    if (lookahead() != tok_obrace)
		eparse_error(head_ast, "expecting '{' %A", head_ast);
	    enew_hash(&ast->l.value, 16);
	    enew_vector(&ast->r.value, t_void, 8);
	    vector = ast->r.value;
	    vector->offset = 1;
	    push_block(ast);
	    statement_noeof();
	    pop_block();
	    ast->c.ast = pop_ast();
	    break;
	case tok_case:
	    switch_case(ast);
	    break;
	case tok_default:
	    if (primary() != tok_collon)
		eparse_error(top_ast(), "expecting ':' %A", top_ast());
	    discard();
	    ast->c.ast = get_block(tok_default, ast);
	    vector = ast->c.ast->r.value;
	    if (vector->v.ptr[0]) {
		east_t		*temp;

		temp = vector->v.ptr[0];
		eparse_error(ast, "duplicate default also at %p:%d:%d",
			     temp->note.name,
			     temp->note.lineno, temp->note.column);
	    }
	    vector->v.ptr[0] = ast;
	    break;
	case tok_break:
	    ast->c.ast = get_block(tok_break, ast);
	    if (primary_noeof() != tok_semicollon)
		eparse_error(top_ast(), "expecting ';' %A", top_ast());
	    discard();
	    break;
	case tok_continue:
	    ast->c.ast = get_block(tok_continue, ast);
	    if (primary_noeof() != tok_semicollon)
		eparse_error(top_ast(), "expecting ';' %A", top_ast());
	    discard();
	    break;
	case tok_goto:
	    if (unary_noeof() != tok_symbol)
		eparse_error(top_ast(), "expecting label %A", top_ast());
	    ast->l.ast = pop_ast();
	    if (primary_noeof() != tok_semicollon)
		eparse_error(top_ast(), "expecting ';' %A", top_ast());
	    discard();
	    break;
	case tok_return:
	    if (lookahead() != tok_semicollon) {
		expression_noeof();
		ast->l.ast = pop_ast();
	    }
	    if (primary_noeof() != tok_semicollon)
		eparse_error(top_ast(), "expecting ';' %A", top_ast());
	    discard();
	    break;
	default:
	    eparse_error(ast, "expecting statement %A", ast);
    }

    return (token);
}

static void
label_define(east_t *ast)
{
    east_t		*block;
    eentry_t		*entry;
    esymbol_t		*symbol;
    eobject_t		*pointer;
    efunction_t		*function;

    if ((block = get_block(tok_label, ast)))
	function = block->t.value;
    else
	function = root_function;

    symbol = ast->l.value;
    if ((entry = eget_hash(function->label_table, symbol->name))) {
	block = entry->value;
	eparse_error(ast, "duplicate label '%p' also at %p:%d:%d",
		     ast->l.value, block->note.name,
		     block->note.lineno, block->note.column);
    }

    gc_ref(pointer);
    enew_entry(pointer, symbol->name, ast);
    eput_hash(function->label_table, *pointer);
    gc_dec();
}

static void
case_value(void)
{
    if (primary() != tok_number)
	eeval_ast(top_ast());
    if (top_ast()->token != tok_number ||
	etype(top_ast()->l.value) != t_word
#if __WORDSIZE == 64
	|| *(eword_t *)(top_ast()->l.value) < -2147483648
	|| *(eword_t *)(top_ast()->l.value) >  2147483647
#endif
	)
	eparse_error(top_ast(), "case value not a 32 bit integer %A",
		     top_ast());
}

static void
case_check_value(east_t *block, east_t *ast)
{
    east_t		*temp;
    eword_t		 lval;
    eword_t		 rval;
    eentry_t		*entry;
    eword_t		 value;
    eword_t		 offset;
    evector_t		*vector;

    if ((entry = eget_hash(block->l.value, ast->l.value))) {
	temp = entry->value;
	eparse_error(ast, "duplicate case %p also at %p:%d:%d",
		     ast->l.value, temp->note.name,
		     temp->note.lineno, temp->note.column);
    }
    vector = block->r.value;
    value = *(eword_t *)ast->l.value;
    for (offset = 1; offset < vector->offset; offset += 3) {
	lval = *(eword_t *)vector->v.ptr[offset];
	rval = *(eword_t *)vector->v.ptr[offset + 1];
	if (lval <= value && rval >= value) {
	    temp = vector->v.ptr[offset + 2];
	    eparse_error(ast, "case range overlaps at %p:%d:%d",
			 temp->note.name, temp->note.lineno, temp->note.column);
	}
    }
}

static void
case_check_range(east_t *block, east_t *ast)
{
    eword_t		 lval;
    eword_t		 rval;
    eword_t		 lrng;
    eword_t		 rrng;
    ehash_t		*hash;
    east_t		*temp;
    eentry_t		*entry;
    eword_t		 offset;
    evector_t		*vector;

    lval = *(eword_t *)ast->l.value;
    rval = *(eword_t *)ast->r.value;
    vector = ast->r.value;
    if (lval > rval)
	eparse_error(ast, "left value larger than right in case range");
    for (offset = 1; offset < vector->offset; offset += 3) {
	lrng = *(eword_t *)vector->v.ptr[offset];
	rrng = *(eword_t *)vector->v.ptr[offset + 1];
	if ((lval <= lrng && rval >= lrng) ||
	    (lval <= rrng && rval >= rrng)) {
	    temp = vector->v.ptr[offset + 2];
	    eparse_error(ast, "case range overlaps at %p:%d:%d",
			 temp->note.name, temp->note.lineno, temp->note.column);
	}
    }
    hash = block->l.value;
    for (offset = 0; offset < hash->size; offset++) {
	for (entry = hash->entries[offset]; entry; entry = entry->next) {
	    lrng = *(eword_t *)entry->name;
	    if (lval <= lrng && rval >= lrng) {
		temp = entry->value;
		eparse_error(ast, "case range overlaps at %p:%d:%d",
			     temp->note.name,
			     temp->note.lineno, temp->note.column);
	    }
	}
    }
}

static void
switch_case(east_t *ast)
{
    east_t		*block;
    evector_t		*vector;
    eobject_t		*pointer;

    case_value();
    ast->l.value = top_ast()->l.value;
    top_ast()->l.value = null;
    discard();
    block = get_block(tok_case, ast);
    ast->c.ast = block;
    switch (primary()) {
	case tok_ellipsis:
	    discard();
	    case_value();
	    ast->r.value = top_ast()->l.value;
	    case_check_range(block, ast);
	    vector = block->r.value;
	    if (vector->offset + 3 >= vector->length)
		erenew_vector(vector, vector->length + 8);
	    vector->v.ptr[vector->offset++] = ast->l.value;
	    vector->v.ptr[vector->offset++] = ast->r.value;
	    vector->v.ptr[vector->offset++] = ast;
	    break;
	case tok_collon:
	    discard();
	    case_check_value(block, ast);
	    gc_ref(pointer);
	    enew_entry(pointer, ast->l.value, ast);
	    eput_hash(block->l.value, *pointer);
	    gc_dec();
	    break;
	default:
	    eparse_error(top_ast(), "expecting ':' %A", top_ast());
    }
}

static void
statement_paren_comma_list(void)
{
    if (primary() != tok_oparen)
	eparse_error(top_ast(), "expecting '(' %A", top_ast());
    discard();
    group_match(false, tok_cparen);
    consume();
    if (top_ast() == null)
	eread_error("expecting comma separated expression list");
}

static east_t *
get_block(etoken_t token, east_t *ast)
{
    east_t		*block;
    eword_t		 offset;

    for (offset = block_vector->offset - 1; offset >= 0; offset--) {
	block = block_vector->v.ptr[offset];
	switch (token) {
	    case tok_case:		case tok_default:
		if (block->token == tok_switch)
		    return (block);
		break;
	    case tok_break:
		switch (block->token) {
		    case tok_for:	case tok_while:
		    case tok_switch:	case tok_do:
			return (block);
		    default:
			break;
		}
		break;
	    case tok_continue:
		switch (block->token) {
		    case tok_for:	case tok_while:
		    case tok_do:
			return (block);
		    default:
			break;
		}
		break;
	    default:
		if (block->token == tok_function)
		    return (block);
		break;
	}
    }

    switch (token) {
	case tok_case:
	    eparse_error(ast, "'case' not in 'switch' %A", ast);
	case tok_default:
	    eparse_error(ast, "'default' not in 'switch' %A", ast);
	case tok_break:
	    eparse_error(ast,
			 "'break' not in 'do', 'for', 'switch', or 'while' %A",
			 ast);
	case tok_continue:
	    eparse_error(ast, "'continue' not in 'do', 'for', or 'while' %A",
			 ast);
	default:
	    /* toplevel function */
	    return (null);
    }
}

static esymbol_t *
declare(east_t *type, east_t *decl)
{
    etag_t		*tag;
    evector_t		*name;
    east_t		*vexp;
    esymbol_t		*symbol;

    if (decl == null)
	eparse_error(type, "expecting declaration %A", type);
    vexp = null;
    while (decl->token == tok_set) {
	vexp = decl->r.ast;
	decl = decl->l.ast;
    }
    tag = etag_ast(type->l.value, &decl);
    if (decl->token == tok_call) {
	(void)prototype(tag, decl);
	return (null);
    }
    if (decl->token != tok_symbol)
	eparse_error(decl, "expecting symbol %A", decl);
    symbol = decl->l.value;
    name = symbol->name;
    if (tag->size == 0) {
	if (tag->type != tag_vector)
	    eparse_error(decl, "expecting vector %A", decl);
	/* patch tag size information of unspecified, but initialized vector */
	if (vexp)
	    tag = etag_ast_data(tag, vexp);
    }

    if (eget_symbol(current_namespace, name)) {
	switch (etype(current_namespace)) {
	    case t_hash:
		eparse_error(decl, "entry '%p' redefined", name);
	    case t_class:
		eparse_error(decl, "field '%p' redefined", name);
	    case t_namespace:
		eparse_error(decl, "symbol '%p' redefined", name);
	    default:
		abort();
	}
    }

    symbol = enew_symbol(current_namespace, name, tag);

    assert(symbol);

    return (symbol);
}

static etoken_t
declaration(void)
{
    east_t		*type;
    east_t		*decl;

    type = top_ast();
    assert(type->token == tok_declexpr);
    type->token = tok_decl;
    decl = type->r.value;
    type = type->l.value;
    assert(type->token == tok_type);
    switch (lookahead()) {
	case tok_comma:
	    consume();
	    group_match(false, tok_semicollon);
	    decl->next = pop_ast();
	case tok_semicollon:
	    consume();
	    break;
	case tok_obrace:
	    function();
	    return (tok_decl);
	default:
	    eparse_error(head_ast, "expecting ',', ';', or '{' %A", head_ast);
    }

    for (; decl; decl = decl->next)
	(void)declare(type, decl);

    return (tok_decl);
}

static efunction_t *
prototype(etag_t *tag, east_t *proto)
{
    east_t		*ast;
    evector_t		*name;
    esymbol_t		*symbol;
    efunction_t		*function;

    assert(proto->token == tok_call);
    proto->token = tok_proto;
    tag = etag_prototype(tag, proto);
    ast = proto->l.ast;
    assert(ast->token == tok_symbol);
    symbol = ast->l.value;
    name = symbol->name;
    if ((function = (efunction_t *)eget_function(name))) {
	if (function->tag != tag)
	    eparse_error(proto, "'%p' redeclared as a different type", name);
    }
    else
	function = enew_function(name, tag);

    return (function);
}

static void
function(void)
{
    east_t		*ast;
    east_t		*type;
    east_t		*list;
    east_t		*proto;
    esymbol_t		*symbol;
    efunction_t		*function;

    if (current_namespace->parent)
	eparse_error(top_ast(), "function not in toplevel");
    ast = top_ast();
    ast->token = tok_function;
    type = ast->l.ast;
    proto = ast->r.ast;
    function = prototype(type->l.value, proto);
    function->ast = ast;
    ast->t.value = function;
    current_namespace = function->namespace;
    if ((list = proto->r.value)) {
	for (; list; list = list->next) {
	    symbol = declare(list->l.value, list->r.value);
	    if (symbol == null)
		eparse_error(list, "syntax error %A", list);
	}
    }

    /* local variables start at this offset */
    function->local_offset = function->namespace->offset;

    push_block(ast);
    statement_noeof();
    pop_block();
    ast->c.ast = pop_ast();
    current_namespace = current_namespace->parent;
}

static etoken_t
structure(void)
{
    east_t		*ast;
    etag_t		*tag;
    east_t		*temp;
    etoken_t		 token;
    erecord_t		*record;
    enamespace_t	*current;
    eobject_t		*pointer;

    switch (primary()) {
	case tok_symbol:
	    ast = top_ast();
	    record = enew_record(ast->l.value);
	    token = lookahead();
	    if (token != tok_obrace) {
		ast->l.value = etag_object(record);
		if (token == tok_comma || token == tok_semicollon)
		    return (ast->token = tok_defn);
		return (ast->token = tok_type);
	    }
	    break;
	case tok_type:
	    ast = top_ast();
	    tag = ast->l.value;
	    if (tag->type != tag_class)
		eparse_error(ast, "%p redeclared as a different type", tag);
	    record = tag->name;
	    assert(etype(record) == t_class);
	    token = lookahead();
	    if (token != tok_obrace) {
		if (token == tok_comma || token == tok_semicollon)
		    return (ast->token = tok_defn);
		return (ast->token = tok_type);
	    }
	    if (record->length)
		eparse_error(head_ast, "class %p redefined", record->name);
	    break;
	default:
	    eparse_error(top_ast(), "expecting symbol or type %A", top_ast());
    }

    consume();
    ast->token = tok_type;
    current = current_namespace;
    current_namespace = (enamespace_t *)record;
    while ((token = expression()) != tok_cbrace) {
	switch (token) {
	    case tok_expr:
		if (top_ast()->token != tok_vector)
		    eparse_error(top_ast(), "expecting declaration %A",
				 top_ast());
	    case tok_symbol:
		/* allow omitting type */
		gc_ref(pointer);
		enew_ast(pointer, tok_declexpr, top_ast()->note.name,
			 top_ast()->note.lineno, top_ast()->note.column);
		temp = *pointer;
		temp->r.ast = top_ast();
		top_ast() = temp;
		gc_dec();
		enew_ast(&temp->l.value, tok_type, top_ast()->note.name,
			 top_ast()->note.lineno, top_ast()->note.column);
		temp->l.ast->l.value = auto_tag;
		token = lookahead();
		if (token != tok_comma && token != tok_semicollon)
		    eparse_error(top_ast(), "expecting declaration %A",
				 top_ast());
	    case tok_decl:
		declaration();
		discard();
		break;
	    default:
		eparse_error(top_ast(), "expecting declaration %A", top_ast());
	}
    }
    discard();
    current_namespace = current;
    eend_record(record);
    if (record->length == 0)
	eparse_error(top_ast(), "empty declaration");
    ast->l.value = etag_object(record);

    return (tok_type);
}

static etoken_t
definition(void)
{
    east_t		*ast;
    etoken_t		 token;

    ast = top_ast();
    token = structure();
    ast->l.ast = pop_ast();
    switch (lookahead()) {
	case tok_comma:
	    if (token != tok_defn)
		eparse_error(head_ast, "expecting ';' %A", head_ast);
	    ast = ast->l.ast;
	    consume();
	    for (;;) {
		if (structure() != tok_defn)
		    eparse_error(top_ast(),
				 "type declaration must be single statement %A",
				 top_ast());
		ast->next = pop_ast();
		ast = ast->next;
		token = lookahead();
		consume();
		if (token == tok_semicollon)
		    break;
		if (token != tok_comma)
		    eparse_error(head_ast, "expecting ',' or ';' %A", head_ast);
	    }
	    break;
	case tok_semicollon:
	    consume();
	    break;
	default:
	    if (token == tok_defn)
		eparse_error(head_ast, "expecting ',' or ';' %A", head_ast);
	    eparse_error(head_ast, "expecting ';' %A", head_ast);
    }

    return (tok_class);
}

static void
group_match(ebool_t data, etoken_t token)
{
    euint8_t		 chr;
    east_t		*ast;

    if (group(data) != token) {
	if ((ast = top_ast())) {
	    for (; ast->next; ast = ast->next)
		;
	}
	else
	    ast = head_ast;
	switch (token) {
	    case tok_semicollon:	chr = ';';	break;
	    case tok_cparen:		chr = ')';	break;
	    case tok_cbrace:		chr = '}';	break;
	    default:			abort();
	}
	eparse_error(ast, "expecting '%c' %A", chr, ast);
    }
}

static etoken_t
group(ebool_t data)
{
    east_t		*ast;
    etoken_t		 token;

    ast = null;
    switch (token = lookahead()) {
	case tok_cparen:	case tok_semicollon:
	    push_ast(null);
	    return (token);
	default:
	    break;
    }
    for (;;) {
	(void)expression();
	if (ast == null)
	    ast = top_ast();
	else {
	    ast->next = pop_ast();
	    ast = ast->next;
	}
	if ((token = lookahead()) == tok_comma) {
	    consume();
	    if (data && lookahead() == tok_cbrace) {
		if (ast == null)
		    push_ast(null);
		return (tok_cbrace);
	    }
	}
	else {
	    if (ast == null)
		push_ast(null);
	    return (token);
	}
    }
}

static etoken_t
precedence_noeof(void)
{
    etoken_t		token = precedence();

    if (token == tok_eof)
	eread_error("unexpected end of file");

    return (token);
}

static etoken_t
precedence(void)
{
    east_t		*ast;
    etoken_t		 next;
    etoken_t		 token;

    for (token = unary();;) {
	switch (token) {
	    case tok_obrace:
		ast = top_ast();
		group_match(true, tok_cbrace);
		consume();
		ast->token = tok_data;
		ast->l.ast = pop_ast();
		return (tok_expr);
	    case tok_list:
		ast = top_ast();
		if (ast->l.ast == null)
		    eparse_error(ast, "expecting expression after '(' %A", ast);
	    case tok_number:		case tok_symbol:
	    case tok_expr:
		switch (next = lookahead()) {
		    case tok_set:	case tok_andset:
		    case tok_orset:	case tok_xorset:
		    case tok_mul2set:	case tok_div2set:
		    case tok_shlset:	case tok_shrset:
		    case tok_addset:	case tok_subset:
		    case tok_mulset:	case tok_divset:
		    case tok_trunc2set:	case tok_remset:
			token = binary_assign();
			break;
		    case tok_andand:	case tok_oror:
		    case tok_lt:	case tok_le:
		    case tok_eq:	case tok_ge:
		    case tok_gt:	case tok_ne:
		    case tok_and:	case tok_or:
		    case tok_xor:	case tok_mul2:
		    case tok_div2:	case tok_shl:
		    case tok_shr:	case tok_add:
		    case tok_sub:	case tok_mul:
		    case tok_div:	case tok_trunc2:
		    case tok_rem:
			token = binary_left(next);
			break;
		    case tok_question:
			(void)primary();
			ast = pop_ast();
			ast->t.ast = top_ast();
			top_ast() = ast;
			precedence_noeof();
			ast->l.ast = pop_ast();
			if (primary() != tok_collon)
			    eparse_error(top_ast(), "expecting ':' %A",
					 top_ast());
			discard();
			precedence_noeof();
			ast->r.ast = pop_ast();
			token = tok_expr;
			break;
		    default:
			return (token);
		}
		break;
	    default:
		return (token);
	}
    }
}

static etoken_t
expression_noeof(void)
{
    etoken_t		token = expression();

    if (token == tok_eof)
	eread_error("unexpected end of file");

    return (token);
}

static etoken_t
expression(void)
{
    etoken_t		token;

    switch (token = precedence()) {
	case tok_symbol:
	    if (lookahead() == tok_collon) {
		consume();
		top_ast()->token = token = tok_label;
	    }
	default:
	    break;
    }

    return (token);
}

static etoken_t
binary_assign(void)
{
    east_t		*last;
    east_t		*mast;
    east_t		*rast;

    for (;;) {
	primary();
	switch (expression()) {
	    case tok_number:		case tok_string:
	    case tok_symbol:		case tok_expr:
		break;
	    default:
		eparse_error(top_ast(), "expecting expression %A", top_ast());
	}
	rast = pop_ast();
	mast = pop_ast();
	last = top_ast();
	mast->l.ast = last;
	mast->r.ast = rast;
	top_ast() = mast;
	/* FIXME:  This conversion should be reworked to use a generic
	 * pointer reference internal object, so that same logic could
	 * be used to class fields and vector elements. */
	/* convert "symbol ?= value" to "symbol = (symbol ? value)" */
	if (last->token == tok_symbol && mast->token != tok_set) {
	    /* temporarily copy to t.ast */
	    mast->t.ast = rast;
	    enew_ast((eobject_t *)&mast->r.ast, tok_expr,
		     mast->note.name, mast->note.lineno, mast->note.column);
	    last = mast->r.ast;
	    enew_ast((eobject_t *)&last->l.ast, tok_symbol,
		     mast->note.name, mast->note.lineno, mast->note.column);
	    last->l.ast->l.value = mast->l.ast->l.value;
	    enew_ast((eobject_t *)&last->r.ast, tok_list,
		     mast->note.name, mast->note.lineno, mast->note.column);
	    /* restore copy */
	    last->r.ast->l.ast = mast->t.ast;
	    mast->t.ast = null;
	    switch (mast->token) {
		case tok_andset:	last->token = tok_and;		break;
		case tok_orset:		last->token = tok_or;		break;
		case tok_xorset:	last->token = tok_xor;		break;
		case tok_mul2set:	last->token = tok_mul2;		break;
		case tok_div2set:	last->token = tok_div2;		break;
		case tok_shlset:	last->token = tok_shl;		break;
		case tok_shrset:	last->token = tok_shr;		break;
		case tok_addset:	last->token = tok_add;		break;
		case tok_subset:	last->token = tok_sub;		break;
		case tok_mulset:	last->token = tok_mul;		break;
		case tok_divset:	last->token = tok_div;		break;
		case tok_trunc2set:	last->token = tok_trunc2;	break;
		default:
		    assert(mast->token == tok_remset);
		    last->token = tok_rem;				break;
	    }
	    mast->token = tok_set;
	}
	switch (lookahead()) {
	    case tok_set:		case tok_andset:
	    case tok_orset:		case tok_xorset:
	    case tok_mul2set:		case tok_div2set:
	    case tok_shlset:		case tok_shrset:
	    case tok_addset:		case tok_subset:
	    case tok_mulset:		case tok_divset:
	    case tok_trunc2set:		case tok_remset:
		break;
	    default:
		return (tok_expr);
	}
    }
}

static etoken_t
binary_left(etoken_t token)
{
    east_t		*last;
    east_t		*mast;
    east_t		*rast;

    for (;;) {
	(void)primary();
	binary_right(token);
	rast = pop_ast();
	mast = pop_ast();
	last = top_ast();
	mast->l.value = last;
	mast->r.value = rast;
	top_ast() = mast;
	switch (lookahead()) {
	    case tok_andand ... tok_oror:
		if (token <= tok_oror)	break;
		return (tok_expr);
	    case tok_ne ... tok_gt:
		if (token <= tok_ne)	break;
		return (tok_expr);
	    case tok_and ... tok_xor:
		if (token <= tok_xor)	break;
		return (tok_expr);
	    case tok_mul2 ... tok_shr:
		if (token <= tok_shr)	break;
		return (tok_expr);
	    case tok_add ... tok_sub:
		if (token <= tok_sub)	break;
		return (tok_expr);
	    case tok_mul ... tok_rem:	break;
	    default:
		return (tok_expr);
	}
    }
}

static void
binary_right(etoken_t token)
{
    etoken_t		next;

    switch (unary()) {
	case tok_number:	case tok_symbol:
	case tok_expr:		case tok_list:
	    break;
	default:
	    eparse_error(top_ast(), "expecting expression %A", top_ast());
    }
    for (;;) {
	switch (next = lookahead()) {
	    case tok_ne ... tok_gt:
		if (token <= tok_oror)	break;
		return;
	    case tok_and ... tok_xor:
		if (token <= tok_gt)	break;
		return;
	    case tok_mul2 ... tok_shr:
		if (token <= tok_xor)	break;
		return;
	    case tok_add ... tok_sub:
		if (token <= tok_shr)	break;
		return;
	    case tok_mul ... tok_rem:
		if (token <= tok_sub)	break;
		return;
	    default:
		return;
	}
	binary_left(next);
    }
}

static etoken_t
unary_noeof(void)
{
    etoken_t		token = unary();

    if (token == tok_eof)
	eread_error("unexpected end of file");

    return (token);
}

static etoken_t
unary(void)
{
    east_t		*ast;
    etoken_t		 token;

    switch (token = primary()) {
	case tok_type:
	    return (unary_decl());
	case tok_symbol:	case tok_string:
	    return (unary_loop(token));
	case tok_oparen:
	    return (unary_list());
	case tok_obrack:
	    ast = top_ast();
	    ast->token = tok_elemref;
	    expression_noeof();
	    ast->l.ast = pop_ast();
	    if (primary_noeof() != tok_cbrack)
		eparse_error(ast, "expecting expression after '[' %A",
			     ast);
	    discard();
	    return (tok_expr);
	case tok_dot:
	    ast = top_ast();
	    ast->token = tok_fieldref;
	    if (primary_noeof() != tok_symbol)
		eparse_error(ast, "expecting symbol %A", ast);
	    ast->l.ast = pop_ast();
	    return (tok_expr);
	case tok_inc:		case tok_dec:
	case tok_add:		case tok_sub:
	case tok_not:		case tok_com:
	    return (unary_value(token));
	case tok_atan2:		case tok_pow:
	case tok_hypot:		case tok_complex:
	case tok_subtypeof:	case tok_renew:
	    return (unary_binary(token));
	case tok_sizeof:	case tok_new:
	    return (unary_unary(token));
	default:
	    return (token);
    }
}

static etoken_t
unary_loop(etoken_t token)
{
    east_t		*ast;
    etoken_t		 next;

    for (;;) {
	switch (next = lookahead()) {
	    case tok_obrack:
		token = tok_expr;
		(void)unary_vector();
		break;
	    case tok_oparen:
		token = tok_expr;
		(void)primary();
		discard();
		group_match(false, tok_cparen);
		ast = head_ast;
		head_ast = null;
		ast->token = tok_call;
		ast->r.ast = pop_ast();
		ast->l.ast = top_ast();
		top_ast() = ast;
		break;
	    case tok_dot:
		token = tok_expr;
		(void)unary_field();
		break;
	    case tok_inc:	case tok_dec:
		token = tok_expr;
		(void)primary();
		ast = pop_ast();
		ast->token = next == tok_inc ? tok_postinc : tok_postdec;
		ast->l.ast = top_ast();
		top_ast() = ast;
		break;
	    default:
		return (token);
	}
    }
}

static etoken_t
unary_value(etoken_t token)
{
    east_t		*ast;
    east_t		*value;

    switch (lookahead()) {
	case tok_oparen:
	case tok_number:	case tok_symbol:
	case tok_string:	case tok_add:
	case tok_sub:		case tok_not:
	case tok_com:
	case tok_atan2:		case tok_pow:
	case tok_hypot:		case tok_complex:
	    break;
	default:
	    eparse_error(top_ast(), "expecting expression %A", top_ast());
    }

    unary();
    ast = pop_ast();
    value = top_ast();
    switch (token) {
	case tok_add:		value->token = tok_plus;	break;
	case tok_sub:		value->token = tok_neg;		break;
	case tok_inc:		case tok_dec:			break;
	case tok_not:		case tok_com:			break;
	default:		eparse_error(value, "internal error");
    }
    value->l.value = ast;

    return (unary_loop(tok_expr));
}

static etoken_t
unary_decl(void)
{
    east_t		*ast;
    etoken_t		 token;
    eobject_t		*pointer;

    ast = top_ast();
    if (ast->token != tok_type)
	return (ast->token);
    token = lookahead();
    if (token == tok_comma || token == tok_cparen)
	return (tok_type);
    if (ast->token == tok_type) {
	/*   If not a type declaration possibly followed by instantiation. */
	gc_ref(pointer);
	enew_ast(pointer, tok_declexpr, top_ast()->note.name,
		 top_ast()->note.lineno, top_ast()->note.column);
	gc_pop(ast);
	ast->l.ast = top_ast();
	top_ast() = ast;
    }
    else if (token == tok_semicollon) {
	/*   Type definition or declaration.
	 *   Code below is a copy of ecode.c:move_left_ast_up() */
	assert(ast->r.ast == null);
	ast->r.ast = ast->l.ast;
	ast->token = ast->l.ast->token;
	ast->l.ast = ast->l.ast->l.ast;
	ast->r.ast->l.value = null;
	edel_object(&ast->r.value);
	return (ast->token);
    }
    ast->token = tok_declexpr;
    switch ((token = expression_noeof())) {
	case tok_symbol:		case tok_expr:
	    break;
	default:
	    eparse_error(top_ast(), "expecting declaration %A", top_ast());
    }
    ast->r.ast = pop_ast();

    return (tok_decl);
}

static etoken_t
unary_list(void)
{
    east_t		*ast, *list;

    group_match(false, tok_cparen);
    consume();
    list = pop_ast();
    ast = top_ast();
    ast->token = tok_list;
    ast->l.value = list;

    return (unary_loop(tok_list));
}

static etoken_t
unary_vector(void)
{
    east_t		*ast;

    ast = head_ast;
    head_ast = null;
    ast->token = tok_vector;
    ast->l.ast = top_ast();
    top_ast() = ast;
    if (expression_noeof() == tok_cbrack) {
	/* avoid need to check if null, as zero is a special value */
	ast->r.ast = pop_ast();
	ast = ast->r.ast;
	ast->token = tok_number;
	eget_word(&ast->l.value);
	*(eword_t *)ast->l.value = 0;
    }
    else if (primary_noeof() != tok_cbrack)
	eparse_error(top_ast(), "expecting ']' %A", top_ast());
    else {
	discard();
	ast->r.ast = pop_ast();
    }

    return (unary_loop(tok_expr));
}

static etoken_t
unary_field(void)
{
    east_t		*ast;

    ast = head_ast;
    head_ast = null;
    ast->l.ast = top_ast();
    top_ast() = ast;
    if (primary_noeof() != tok_symbol)
	eparse_error(top_ast(), "expecting symbol %A", top_ast());
    ast->r.ast = pop_ast();

    return (unary_loop(tok_expr));
}

static etoken_t
unary_unary(etoken_t token)
{
    east_t		*ast;
    ebool_t		 paren;

    ast = top_ast();
    if ((paren = lookahead() == tok_oparen))
	consume();
    else if (token != tok_sizeof && token != tok_new)
	eparse_error(ast, "expecting '(' %A", ast);
    expression_noeof();
    ast->l.ast = pop_ast();
    if (paren) {
	if (primary_noeof() != tok_cparen)
	    eparse_error(ast, "expecting ')' %A", ast);
	discard();
    }

    return (tok_expr);
}

static etoken_t
unary_binary(etoken_t token)
{
    east_t		*ast;

    ast = top_ast();
    if (primary_noeof() != tok_oparen)
	eparse_error(top_ast(), "expecting '(' %A", top_ast());
    discard();
    expression_noeof();
    ast->l.ast = pop_ast();
    if (primary_noeof() != tok_comma)
	eparse_error(top_ast(), "expecting ',' %A", top_ast());
    discard();
    expression_noeof();
    ast->r.ast = pop_ast();
    if (primary_noeof() != tok_cparen)
	eparse_error(top_ast(), "expecting ')' %A", top_ast());
    discard();

    return (tok_expr);
}

static etoken_t
primary_noeof(void)
{
    etoken_t		token = primary();

    if (token == tok_eof)
	eread_error("unexpected end of file");

    return (token);
}

static etoken_t
primary(void)
{
    etoken_t		token;
    eobject_t		object;

    if (head_ast) {
	token = head_ast->token;
	push_ast(head_ast);
	head_ast = null;
	return (token);
    }

    if ((object = eread_object()) == object_eof)
	return (tok_eof);
    gc_push(object);

    return (tokenize());
}

static etoken_t
lookahead(void)
{
    if (head_ast == null) {
	if (primary() == tok_eof)
	    return (tok_eof);
	head_ast = pop_ast();
    }

    return (head_ast->token);
}

static etoken_t
tokenize(void)
{
    east_t		*ast;
    etoken_t		 token;
    eobject_t		 object;
    eobject_t		*pointer;

    gc_top(object);
    switch (etype(object)) {
	case t_word:		case t_float:		case t_rat:
	case t_mpz:		case t_mpq:		case t_mpr:
	case t_cdd:		case t_mpc:
	    token = tok_number;
	    break;
	case t_string:
	    token = tok_string;
	    break;
	case t_symbol:
	    token = tok_symbol;
	    break;
	case t_ast:
	    ast = object;
	    push_ast(ast);
	    gc_dec();
	    return (ast->token);
	default:
	    eread_error("internal error (unexpected object type)");
    }

    gc_ref(pointer);
    enew_ast(pointer, token, input_note.name, input->lineno, input->column);
    gc_pop(ast);
    gc_pop(ast->l.value);
    push_ast(ast);

    return (ast->token);
}

static void
consume(void)
{
    assert(head_ast);
    edel_object((eobject_t *)&head_ast);
}

static void
discard(void)
{
    assert(parser_vector->offset > 0);
    edel_object((eobject_t *)(ast_vector + --parser_vector->offset));
}

static void
push_ast(east_t *ast)
{
    if (parser_vector->offset >= parser_vector->length) {
	erenew_vector(parser_vector, 16);
	ast_vector = parser_vector->v.obj;
    }
    ast_vector[parser_vector->offset++] = ast;
}

static east_t *
pop_ast(void)
{
    assert(parser_vector->offset > 0);
    return (ast_vector[--parser_vector->offset]);
}

static void
push_block(east_t *ast)
{
    if (block_vector->offset >= block_vector->length)
	erenew_vector(block_vector, 16);
    block_vector->v.ptr[block_vector->offset++] = ast;
}

static void
pop_block(void)
{
    assert(block_vector->offset > 0);
    block_vector->v.ptr[--block_vector->offset] = null;
}
